﻿using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using NetTopologySuite.Planargraph;
using NetTopologySuite.Utilities;

namespace NetTopologySuite.Operation.Linemerge
{
	public class LineMerger
	{
		public void Add(IGeometry geometry)
		{
			geometry.Apply(new LineMerger.AnonymousGeometryComponentFilterImpl(this));
		}

		public void Add(IEnumerable<IGeometry> geometries)
		{
			this._mergedLineStrings = null;
			foreach (IGeometry geometry in geometries)
			{
				this.Add(geometry);
			}
		}

		private void Add(ILineString lineString)
		{
			if (this._factory == null)
			{
				this._factory = lineString.Factory;
			}
			this._graph.AddEdge(lineString);
		}

		private void Merge()
		{
			if (this._mergedLineStrings != null)
			{
				return;
			}
			GraphComponent.SetMarked(this._graph.GetNodeEnumerator(), false);
			GraphComponent.SetMarked(this._graph.GetEdgeEnumerator(), false);
			this._edgeStrings = new List<EdgeString>();
			this.BuildEdgeStringsForObviousStartNodes();
			this.BuildEdgeStringsForIsolatedLoops();
			this._mergedLineStrings = new List<IGeometry>();
			foreach (EdgeString edgeString in this._edgeStrings)
			{
				this._mergedLineStrings.Add(edgeString.ToLineString());
			}
		}

		private void BuildEdgeStringsForObviousStartNodes()
		{
			this.BuildEdgeStringsForNonDegree2Nodes();
		}

		private void BuildEdgeStringsForIsolatedLoops()
		{
			this.BuildEdgeStringsForUnprocessedNodes();
		}

		private void BuildEdgeStringsForUnprocessedNodes()
		{
			foreach (Node node in this._graph.Nodes)
			{
				if (!node.IsMarked)
				{
					Assert.IsTrue(node.Degree == 2);
					this.BuildEdgeStringsStartingAt(node);
					node.Marked = true;
				}
			}
		}

		private void BuildEdgeStringsForNonDegree2Nodes()
		{
			foreach (Node node in this._graph.Nodes)
			{
				if (node.Degree != 2)
				{
					this.BuildEdgeStringsStartingAt(node);
					node.Marked = true;
				}
			}
		}

		private void BuildEdgeStringsStartingAt(Node node)
		{
			foreach (DirectedEdge directedEdge in node.OutEdges)
			{
				LineMergeDirectedEdge lineMergeDirectedEdge = (LineMergeDirectedEdge)directedEdge;
				if (!lineMergeDirectedEdge.Edge.IsMarked)
				{
					this._edgeStrings.Add(this.BuildEdgeStringStartingWith(lineMergeDirectedEdge));
				}
			}
		}

		private EdgeString BuildEdgeStringStartingWith(LineMergeDirectedEdge start)
		{
			EdgeString edgeString = new EdgeString(this._factory);
			LineMergeDirectedEdge lineMergeDirectedEdge = start;
			do
			{
				edgeString.Add(lineMergeDirectedEdge);
				lineMergeDirectedEdge.Edge.Marked = true;
				lineMergeDirectedEdge = lineMergeDirectedEdge.Next;
			}
			while (lineMergeDirectedEdge != null && lineMergeDirectedEdge != start);
			return edgeString;
		}

		public IList<IGeometry> GetMergedLineStrings()
		{
			this.Merge();
			return this._mergedLineStrings;
		}

		private readonly LineMergeGraph _graph = new LineMergeGraph();

		private List<IGeometry> _mergedLineStrings;

		private List<EdgeString> _edgeStrings;

		private IGeometryFactory _factory;

		private class AnonymousGeometryComponentFilterImpl : IGeometryComponentFilter
		{
			public AnonymousGeometryComponentFilterImpl(LineMerger container)
			{
				this._container = container;
			}

			public void Filter(IGeometry component)
			{
				if (component is ILineString)
				{
					this._container.Add((ILineString)component);
				}
			}

			private readonly LineMerger _container;
		}
	}
}
